//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: Transaction.cs
//
//  Description: Provides a transaction object for MS SQL Server.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

namespace Chili.Opf3.Storages.MsSql
{
	/// <summary>
	/// Class that provides a transaction object for the <see cref="MsSqlStorage">MsSqlStorage</see> class.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class provides a transaction object for the <see cref="MsSqlStorage">MsSqlStorage</see> class.
	/// It is returned by the <see cref="MsSqlStorage.StartTransaction">StartTransaction</see> methods of 
	/// the class.
	/// </para>
	/// <para>
	/// MsSqlTransaction adds support for save points and rolling back to save points.
	/// </para>
	/// </remarks>
	public class MsSqlTransaction : SqlTransactionBase
	{
		private static long _rootIndex;
		
		private Stack<MsSqlSavePoint> _savePoints = new Stack<MsSqlSavePoint>();
		private string _root = "__msql_spoint__#";
		
		/// <summary>
		/// Creates a new instance of the <see cref="MsSqlTransaction">MsSqlTransaction Class</see>.
		/// </summary>
		/// <param name="transaction">The transaction that is returned by the ADO.NET provider for MS SQL.</param>
		internal MsSqlTransaction(SqlTransaction transaction)
			: base(transaction)
		{
			_rootIndex = (_rootIndex + 1) % (long.MaxValue - 1);
			_root = _root + _rootIndex;

			CreateSavePoint(_root);
		}

		/// <summary>
		/// Returns the root save point of the current transaction.
		/// </summary>
		protected string Root
		{
			get { return _root; }
		}

		/// <summary>
		/// Adds the current <see cref="Opf3.ObjectInfo">ObjectInfo</see> object. This is required to roll
		/// the state of the persistent back during a roll back.
		/// </summary>
		/// <param name="oi"><see cref="Opf3.ObjectInfo">ObjectInfo</see> that is backed up.</param>
		/// <returns>Whether the <see cref="Opf3.ObjectInfo">ObjectInfo</see> has been added.</returns>
		protected override void AddObjectInfo(ObjectInfo oi)
		{
			base.AddObjectInfo(oi);

			MsSqlSavePoint sp = _savePoints.Peek();
			sp.ObjectInfos.Add(oi);
		}

		/// <summary>
		/// Creates a save point in the transaction that can be used to roll back a part of the transaction, 
		/// and specifies the save point name.
		/// </summary>
		/// <param name="name">The name of the save point to create.</param>
		/// <returns>An instance of the <see cref="MsSqlSavePoint">MsSqlSavePoint</see> class.</returns>
		public MsSqlSavePoint CreateSavePoint(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			// Add the save point to the transaction.
			((SqlTransaction)base.InnerTransaction).Save(name);

			// Create the new save point.
			MsSqlSavePoint sp = new MsSqlSavePoint(name);
			_savePoints.Push(sp);

			return sp;
		}

		/// <summary>
		/// Rolls back a transaction from a pending state, and specifies the transaction 
		/// or save point name.
		/// </summary>
		/// <param name="savePoint">The save point to roll back to.</param>
		public void Rollback(MsSqlSavePoint savePoint)
		{
			if (savePoint == null)
				throw new ArgumentNullException("savePoint");

			this.Rollback(savePoint.Name);
		}

		/// <summary>
		/// Rolls back a transaction from a pending state, and specifies the transaction 
		/// or save point name.
		/// </summary>
		/// <param name="savePoint">The name of the save point to roll back to.</param>
		public void Rollback(string savePoint)
		{
			if (savePoint == null)
				throw new ArgumentNullException("savePoint");

			((SqlTransaction)base.InnerTransaction).Rollback(savePoint);

			MsSqlSavePoint sp = null;
			while((sp = _savePoints.Pop()) != null)
			{
				foreach (ObjectInfo os in sp.ObjectInfos)
				{
					base.RollbackObjectInfo(os);
				}

				if (sp.Name == savePoint)
					return;
			}
		}

		/// <summary>
		/// Rolls the current transaction back.
		/// </summary>
		public override void Rollback()
		{
			base.Rollback();
			// Clear the savepoints.
			_savePoints.Clear();
		}
	}
}
